import java.io.*;
import java.net.*;
import java.util.ArrayList;

//import java.security.*;

/**
 * Oversees all communication and execution
 */

public class RubikServer {

	// private static int difficulty;
	public static Boolean Ready = false;
	public static String Test;
	public static String Winner;
	private static int lastInt = 0;

	private static PortListener portListener;
	public static TestCreator testCreator = new TestCreator();
	public static ArrayList<PortCommunicator> players = new ArrayList<PortCommunicator>();
	private static ArrayList<Thread> playerThreads = new ArrayList<Thread>();

	// Listen for incoming connections and handle them
	public static void main(String[] args) {

		// Introduction text
		System.out
				.println("\nWelcome to the ECE462 Rubik's Competition Server!");

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String input;

		// Receiving startup values for server. Loop in case of invalid integer
		// value.
		while (true) {
			try {
				// Get values for port number and max connections
				portListener = InitializeServer(br);
				System.out.println("Starting server... \n");
				break;
			} catch (IOException ioe) {
				ioe.printStackTrace();
				System.exit(1);
			} catch (NumberFormatException nfe) {
				System.out.println(nfe + "\nRestarting initialization...");
			}
		}

		// Initial setup will allow for a connection period followed by a
		// testing period. New tests between different parties will require a
		// restart.
		System.out.println("Joining period beginning...");
		Thread portThread = new Thread(portListener);
		portThread.setDaemon(true);
		System.out.println("Opening port...");
		portThread.start();
		System.out
				.println("Port opened.  Press enter when all competitors have joined.");
		try {
			br.readLine();
		} catch (IOException ioe) {
			ioe.printStackTrace();
			System.exit(1);
		}
		portListener.Stop();
		portThread.interrupt();

		// Begin competition:
		System.out.println("\nBeginning competition (type 'END' to quit)");
		System.out.print("Enter test file name: ");
		try {
			while (!(input = br.readLine().trim()).startsWith("END")) {
				lastInt = 0;
				// Create test to send to threads
				Test = testCreator.LoadTestFromFile(input);
				if (Test.isEmpty()) {
					System.out.print("Enter test file name: ");
					continue;
				}

				// Set off threads to oversee port communication
				System.out.println("Starting test...");
				for (PortCommunicator player : players) {
					Thread thread = new Thread(player);
					thread.setDaemon(true);
					playerThreads.add(thread);
					thread.start();
				}

				// Await ready flag from all competitors
				System.out.println("Awaiting Ready signals...");
				while (numReady() < NumConnections()) {
					Thread.yield();
				}
				Ready = true;

				// Start timer
				int start = (int) (System.currentTimeMillis() / 1000F);

				// Await threads to finish run/test
				System.out.println("Waiting for players to finish...");
				Boolean waiting = true;
				while (waiting) {
					Thread.yield();
					waiting = false;

					for (Thread thread : playerThreads) {
						if (thread.isAlive()) {
							waiting = true;
						}
					}

					// Check if it's time to send a time message
					int pres = (int) (System.currentTimeMillis() / 1000F);
					if (isInterval(pres - start)) {
						// if time is up (10 min)
						if (pres - start > 600) {
							killActiveThreads();
						} else {
							for (PortCommunicator player : players) {
								if (player.Running) {
									player.SendTimeMessage(pres - start);
								}
							}
						}
					}
				}
				Ready = false;

				// Stop timer

				System.out.print("Enter next test file name: ");
			}

			// Find winner
			int min = Integer.MAX_VALUE;
			for (PortCommunicator player : players) {
				if (player.CommandCount < min) {
					min = player.CommandCount;
				}
			}
			// Send win/loss messages
			for (PortCommunicator player : players) {
				if (player.CommandCount == min) {
					player.PrintWinner();
				} else {
					player.PrintLoser();
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}

		System.out.println("Shutting down server...");
	}

	// Add new socket objects with each new player
	public static void AddSocket(Socket socket) {
		PortCommunicator pc = new PortCommunicator(socket);
		players.add(pc);
	}

	private static int numReady() {
		int ready = 0;
		for (PortCommunicator p : players) {
			if (p.IsReady()) {
				ready++;
			}
		}
		return ready;
	}

	public static int NumConnections() {
		return players.size();
	}

	private static Boolean isInterval(int numSeconds) {
		if (numSeconds < 540) {
			if (numSeconds - lastInt > 120) {
				lastInt = numSeconds;
				return true;
			}
		} else if (numSeconds < 570) {
			if (numSeconds - lastInt > 60) {
				lastInt = numSeconds;
				return true;
			}
		} else if (numSeconds < 600) {
			if (numSeconds - lastInt > 30) {
				lastInt = numSeconds;
				return true;
			}
		} else {
			return true;
		}
		return false;
	}

	private static void killActiveThreads() {
		// tell each player still running they lose
		for (PortCommunicator player : players) {
			if (player.Running) {
				player.PrintLoser();
				players.remove(player);
			}
		}
		// kill active threads
		for (Thread thread : playerThreads) {
			if (thread.isAlive()) {
				thread.interrupt();
				playerThreads.remove(thread);
			}
		}
	}

	public static void ConnectionClosed(PortCommunicator pc) {
		players.remove(pc);
	}

	private static PortListener InitializeServer(BufferedReader br)
			throws IOException, NumberFormatException {
		System.out.print("Port number (must be > 1023): ");
		int port = Integer.parseInt(br.readLine());
		System.out.print("Max connections (0 for inf): ");
		int maxConnections = Integer.parseInt(br.readLine());
		return new PortListener(port, maxConnections);
	}
}
